package demo.hdz.mediacodecdecode;

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.os.Build;
import android.util.Log;
import android.view.Surface;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import demo.hdz.mediacodecdecode.interfaces.IDecodeOperate;

/**
 *
 * @author hdz
 * @date 2018/1/20
 */

public class AvcDecoder implements IDecodeOperate {

    private static final String TAG = "AvcDecoder";
    public static final String MIME_TYPE = "video/avc";
    private static final int FRAME_RATE = 30;

    private int mFrameCount = 0;
    private int mValidFrame = 0;
    private MediaCodec mMediaCodec = null;
    private Lock mLock = new ReentrantLock();
    private boolean mPause = false;
    private boolean mStop = false;


    /**
     * 初始化并创建解码器
     * **/
    public boolean init(int w, int h, Surface surface, int iRotation) {
        Logger.d(TAG+",init: " + w + "x" + h + ", Rotation="+iRotation);
        //帧计数
        mFrameCount = 0;

        //创建解码器
        try {
            if (mMediaCodec == null) {
                mMediaCodec = MediaCodec.createDecoderByType(MIME_TYPE);
                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                    Logger.d(TAG + ",init: mediaCodec name: " + mMediaCodec.getCodecInfo().getName());
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            Logger.e(Log.getStackTraceString(e));
            return false;
        }

        Logger.d("setSurface: " + surface);

        mMediaCodec.stop();

        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, w, h);
        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //图像旋转
            format.setInteger(MediaFormat.KEY_ROTATION, iRotation);
        }

        if (!surface.isValid()) {
            Logger.d(TAG, "Surface is invalid!");
            return false;
        }

        //配置解码器
        try {
            mMediaCodec.configure(format, surface, null, 0);
        } catch (Exception e) {
            e.printStackTrace();
            Logger.e(Log.getStackTraceString(e));
            return false;
        }

        //开始解码
        mMediaCodec.start();

        return true;
    }


    public void decodeFile(InputStream inputStream) {
        //文件流
        if (inputStream == null) {
            return;
        }
        while (true) {
            if (mPause) {
                continue;
            }
            if (mStop) {
                return;
            }
            try {
                byte[] headData = new byte[4];
                int ret = inputStream.read(headData, 0, headData.length);
                if (ret == headData.length) {
                    ByteBuffer byteBuffer = ByteBuffer.wrap(Arrays.copyOf(headData, 4));
                    byteBuffer.order(ByteOrder.LITTLE_ENDIAN);

                    int dataLen = byteBuffer.getInt(0);
                    //Logger.d("AvcDecoder, dataLen: " + dataLen);

                    byte[] inData = new byte[dataLen];
                    ret = inputStream.read(inData, 0, inData.length);
                    if (ret == inData.length) {
                        //解码一帧数据
                        decode(inData);
                    } else {
                        inputStream.close();
                        break;
                    }
                } else {
                    inputStream.close();
                    break;
                }
                //不要跑太快，休眠一下，用于测试
                Thread.sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
                Logger.e(Log.getStackTraceString(e));
            }
        }
        decode(null);
    }


    /**
     * 具体流程：
     *　　1.获取一个可用的inputBuffer的索引(出列)
     *　　2.将一帧数据放入inputBuffer
     *　　3.将inputBuffer入列进行解码
     *　　4.获得一个outputBuffer的索引(出列)
     *　　5.释放outputBuffer
     *　　6.在4,5间循环直到没有outputBuffer可出列为止
     *
     * format naul + data:  00 00 00 01 xxxxxx
     **/
    private void decode(byte[] input) {
        long startTime = System.currentTimeMillis();
        long tmpTime = 0;

        mLock.lock();

        try {
            //获取输入缓冲区
            ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();

            boolean inputSuccess = false;
            while (!inputSuccess) {

                //获取可用缓冲区位置的索引
                //如果存在可用的缓冲区，此方法会返回其位置索引，否则返回-1，参数为超时时间，单位是微秒，
                // 如果此参数是0，则立即返回，如果参数小于0，则无限等待直到有可使用的缓冲区，如果参数大于0，则等待时间为传入的毫秒值。
                int inputBufferIndex = mMediaCodec.dequeueInputBuffer(100*1000);
                //Logger.d("Frame index of the first frame: " + inputBufferIndex);

                if (inputBufferIndex >= 0) {
                    long presentationTimeUs = mFrameCount++ * 1000 * 1000 / FRAME_RATE;
                    ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                    //清除原来的内容以接收新的内容
                    inputBuffer.clear();
                    if (input != null) {
                        //向输入缓冲区填充数据
                        inputBuffer.put(input);
                        mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, presentationTimeUs, 0);
                        //Logger.d("input decode frame: " + mFrameCount);
                    } else {
                        mMediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                    }
                    inputSuccess = true;
                }

                tmpTime = System.currentTimeMillis();

                MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                long timeoutUs = 1000 * 100;

                //获取其输出数据
                int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, timeoutUs);
                if (outputBufferIndex >= 0) {
                    mMediaCodec.releaseOutputBuffer(outputBufferIndex, true);
                    mValidFrame++;
                    //Logger.d("output decode frame: " + mValidFrame);
                }else if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    //Logger.d("INFO_TRY_AGAIN_LATER: time out = "+timeoutUs+"us");
                } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    MediaFormat format = mMediaCodec.getOutputFormat();
                    Logger.d("onOutputFormatChanged: " + format.getInteger(MediaFormat.KEY_COLOR_FORMAT)
                            + ", size: " + format.getInteger(MediaFormat.KEY_WIDTH) + "x" + format.getInteger(MediaFormat.KEY_HEIGHT));
                } else if (outputBufferIndex < 0) {
                    Logger.d("outputBufferIndex < 0");
                }
            }
        } catch (Throwable t) {
            t.printStackTrace();
            Logger.e(Log.getStackTraceString(t));
        }
        mLock.unlock();

        long endTime = System.currentTimeMillis();
        //Logger.d("decode a frame use time: " + (endTime-startTime) + "ms, decode time: " + (endTime-tmpTime));
    }

    public void close() {
        try {
            mMediaCodec.stop();
            mMediaCodec.release();
            mMediaCodec = null;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void startOrPause(boolean pause) {
        mPause = pause;
    }

    @Override
    public void stop() {
        mStop = true;
    }
}

